Skip to content

Fix re-login hang on auth page after logout#5535

Draft
FadhlanR wants to merge 2 commits into
mainfrom
cs-12207-google-signin-logout-bug
Draft

Fix re-login hang on auth page after logout#5535
FadhlanR wants to merge 2 commits into
mainfrom
cs-12207-google-signin-logout-bug

Conversation

@FadhlanR

Copy link
Copy Markdown
Contributor

Problem

After logging out, the next in-app login (any method) hangs indefinitely on the auth page: the form submits but never progresses. Reliably reproduced via Google SSO → logout → email/password login → stuck, but the root cause is not SSO-specific — any logout → in-app re-login (without a page reload) hits it.

Root cause

MatrixService is a singleton. Its client-ready barrier #clientReadyDeferred is only ever fulfilled once, at boot inside loadSDK() — the sole call site of .fulfill().

Logout stays in-app (router.transitionTo, not a page reload), and its finally runs resetState(). resetState() recreates the matrix client synchronously but then replaces #clientReadyDeferred with a fresh, never-fulfilled Deferred. Nothing re-runs loadSDK(), so the new deferred stays pending for the life of the singleton.

The next login → realm auth → createRealmSession() does await this.#clientReadyDeferred.promise. That promise never resolves → start() never returns → the login task never settles → the auth page stays mounted.

Regression introduced in f3fb792bce ("Memory leak cleanup"): before it, resetState() left the deferred fulfilled. The cleanup recreated it to drop references but overlooked that it is only fulfilled once, at boot.

Fix

In resetState(), fulfill the freshly created #clientReadyDeferred when _client exists (the client is already recreated synchronously just above). This restores the invariant "the deferred is fulfilled whenever _client exists" while keeping the memory-cleanup intent of a stale-reference-free deferred. The _client guard covers the edge case where the SDK hasn't loaded yet.

Testing

New integration test matrix-service-relogin-test.ts asserts createRealmSession settles after a resetState() instead of hanging (raced against a timeout guard so a hang fails the test rather than stalling the suite).

Verified headless:

  • Fails against pre-fix code — times out on the hang.
  • Passes with the fix; full matrix-service integration filter is green (22/22).

🤖 Generated with Claude Code

…tate)

MatrixService is a singleton whose client-ready barrier
(#clientReadyDeferred) was only ever fulfilled once, at boot in loadSDK().
Logout stays in-app (a router transition, not a page reload) and its finally
runs resetState(), which recreates the client synchronously but then replaced
#clientReadyDeferred with a fresh, never-fulfilled Deferred. The next login
awaits that deferred in createRealmSession(), so it never resolves and the auth
page hangs.

Fulfill the freshly created deferred when _client exists, restoring the
invariant "the deferred is fulfilled whenever _client exists" while keeping the
memory-cleanup intent of a stale-reference-free deferred. Add an integration
regression test covering re-login after a resetState().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Preview deployments

Host Test Results

    1 files  ±0      1 suites  ±0   2h 40m 27s ⏱️ -9s
3 549 tests +1  3 534 ✅ +1  15 💤 ±0  0 ❌ ±0 
3 568 runs  +1  3 553 ✅ +1  15 💤 ±0  0 ❌ ±0 

Results for commit 730b3df. ± Comparison against earlier commit 12d188b.

Realm Server Test Results

    1 files  ±0      1 suites  ±0   17m 14s ⏱️ +30s
1 880 tests +5  1 880 ✅ +5  0 💤 ±0  0 ❌ ±0 
1 959 runs  +5  1 959 ✅ +5  0 💤 ±0  0 ❌ ±0 

Results for commit 730b3df. ± Comparison against earlier commit 12d188b.

resetState() now leaves the client-ready barrier untouched instead of
recreating and immediately fulfilling it — a once-fulfilled deferred stays
fulfilled, and the reset recreates the client synchronously whenever the
SDK is loaded, so the two are equivalent.

The new matrix E2E test covers the full in-app logout -> login-form ->
re-login flow that the host integration test can't: logout is a router
transition, not a page reload, so the second login runs against the
client that resetState() recreated. The test waits for logout's
transition to index-root before filling the form, since that transition
remounts the login form and would wipe an earlier fill.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant